package dominio;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Nodo {
	
	private Posicion posicion;
	
	private Map<Sentido, Nodo> vecinos;
	
	private LaberintoObject contiene;
	
	public Nodo() {
		vecinos = new HashMap<Sentido, Nodo>();
	}
	 
	public LaberintoObject getContiene() {
		return contiene;
	}

	public Nodo setContiene(LaberintoObject contiene) {
		this.contiene = contiene;
		return this;
	}

	public Posicion getPosicion() {
		return posicion;
	}

	public Nodo setPosicion(Posicion posicion) {
		this.posicion = posicion;
		return this;
	}

	public Nodo getIzquierda() {
		return vecinos.get(Sentido.IZQUIERDA);
	}

	public Nodo setIzquierda(Nodo izquierda) {
		vecinos.put(Sentido.IZQUIERDA, izquierda);
		return this;
	}

	public Nodo getDerecha() {
		return vecinos.get(Sentido.DERECHA);
	}

	public Nodo setDerecha(Nodo derecha) {
		vecinos.put(Sentido.DERECHA, derecha);
		return this;
	}

	public Nodo getArriba() {
		return vecinos.get(Sentido.ARRIBA);
	}

	public Nodo setArriba(Nodo arriba) {
		vecinos.put(Sentido.ARRIBA, arriba);
		return this;
	}

	public Nodo getAbajo() {
		return vecinos.get(Sentido.ABAJO);
	}

	public Nodo setAbajo(Nodo abajo) {
		vecinos.put(Sentido.ABAJO, abajo);
		return this;
	}
	
	public Nodo getVecino(Sentido sentido) {
		return vecinos.get(sentido);		 
	}
	
	public Nodo setVecino(Sentido sentido, Nodo vecino) {
		vecinos.put(sentido, vecino);
		return this;
	}

	public boolean isPared(){
		boolean ret = true;
		Set<Sentido> keySet = vecinos.keySet();
		for (Sentido sentido : keySet) {
			Nodo nodo = vecinos.get(sentido);
			ret &= (nodo == null);
		}
		return ret;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Nodo other = (Nodo) obj;
		if (posicion == null) {
			if (other.posicion != null)
				return false;
		} else if (!posicion.equals(other.posicion))
			return false;
		
		return true;
	}

	public Integer getCantidadVecinos() {
		return getVecinos().size();
	}
	
	public Sentido getDireccion(Nodo vecino) {
		for (Sentido s : vecinos.keySet()) {
			if ((vecinos.get(s) != null) && (vecinos.get(s).equals(vecino))) {
				return s;
			}
		}
		return null;
	}
	
	public Collection<Nodo> getVecinos() {
		List<Nodo> nodos = new ArrayList<Nodo>();
		for (Nodo nodo : this.vecinos.values()) {
			if (nodo != null) {
				nodos.add(nodo);
			}
		}
		return nodos;
	}

	public Integer getDistancia(Nodo otroNodo) {
		if (otroNodo != null) {
			return calcularDistancia(new HashSet<Nodo>(), this, otroNodo, null);
		} else {
			return null;
		}
	}
	
	public Integer getDistancia(Nodo otroNodo, Integer distanciaMax) {
		if (otroNodo != null) {
			return calcularDistancia(new HashSet<Nodo>(), this, otroNodo, distanciaMax);
		} else {
			return null;
		}
	}
	
	private static Integer calcularDistancia(Set<Nodo> nodosRecorridos, Nodo actual, 
										Nodo objetivo, Integer distanciaMax) {
		
		if (((distanciaMax != null) && (nodosRecorridos.size() > distanciaMax)) || (nodosRecorridos.contains(actual))) {
			return null;
		}
		
		if (objetivo.equals(actual)) {
			return nodosRecorridos.size();
		}
		
		Integer menorDistancia = distanciaMax;
		Boolean caminoEncontrado = false;
		nodosRecorridos.add(actual);
		for (Sentido sentido : actual.vecinos.keySet()) {
			if ( (actual.vecinos.get(sentido) != null) && (!nodosRecorridos.contains(actual.vecinos.get(sentido))) ) {
				Integer aux = calcularDistancia(nodosRecorridos, actual.getVecino(sentido), objetivo, menorDistancia);
				if ((aux != null) && ( (menorDistancia == null) || (aux <= menorDistancia) )) {
					menorDistancia = aux;
					caminoEncontrado = true;
				}
			}
		}
		nodosRecorridos.remove(actual);
		
		if (caminoEncontrado) {
			return menorDistancia;			
		} else {
			return null;
		}
	}

}
